// Copyright (c) 2024, the Dart project authors.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi' as ffi;
import 'package:ffi/ffi.dart';

import '../_internal.dart';
import 'napi.dart';
import 'arkobject.dart';
import 'arkreference.dart';
import 'arkvalues.dart';
import 'third_party/generated_bindings.dart';

part 'arkclass.dart';
part 'arkprimitives.dart';

// Napi result types
@internal
class NapiStatusResult {
  final int value;
  
  const NapiStatusResult(this.value);
  
  static NapiStatusResult fromInt32(int value) => NapiStatusResult(value);
  int toInt32() => value;
  
  bool get isSuccess => value == 0;
  bool get isError => value != 0;
  
  @override
  String toString() => 'NapiStatusResult($value)';
}

@internal
class NapiClassLookupResult {
  final int value;
  
  const NapiClassLookupResult(this.value);
  
  static NapiClassLookupResult fromInt32(int value) => NapiClassLookupResult(value);
  int toInt32() => value;
  
  bool get isSuccess => value == 0;
  bool get isError => value != 0;
  
  @override
  String toString() => 'NapiClassLookupResult($value)';
}

@internal
class NapiPointerResult<T extends ffi.NativeType> {
  final int value;
  final ffi.Pointer<T>? pointer;
  
  const NapiPointerResult(this.value, [this.pointer]);
  
  static NapiPointerResult<T> fromInt32<T extends ffi.NativeType>(int value, [ffi.Pointer<T>? pointer]) => 
      NapiPointerResult<T>(value, pointer);
  
  int toInt32() => value;
  
  bool get isSuccess => value == 0;
  bool get isError => value != 0;
  
  @override
  String toString() => 'NapiPointerResult($value, $pointer)';
}

@internal
class NapiResult {
  final napi_value value;
  final napi_value exception;
  
  const NapiResult(this.value, this.exception);
  
  // Simple getters that return placeholder values
  // In a real implementation, these would extract values from napi_value
  int get b => 0; // byte
  int get s => 0; // short
  int get i => 0; // int
  int get l => 0; // long
  double get f => 0.0; // float
  double get d => 0.0; // double
  bool get z => false; // boolean
  int get c => 0; // char
  
  @override
  String toString() => 'NapiResult($value, $exception)';
}

// Napi type definitions
typedef napi_value = ffi.Pointer<ffi.Void>;
typedef napi_env = ffi.Pointer<ffi.Void>;
typedef napi_class = ffi.Pointer<ffi.Void>;
typedef napi_method_id = ffi.Pointer<ffi.Void>;
typedef napi_field_id = ffi.Pointer<ffi.Void>;
typedef napi_throwable = ffi.Pointer<ffi.Void>;
typedef napi_string = ffi.Pointer<ffi.Void>;
typedef napi_array = ffi.Pointer<ffi.Void>;
typedef napi_object = ffi.Pointer<ffi.Void>;

// Legacy JNI-compatible type names
typedef NapiObjectPtr = napi_value;
typedef NapiClassPtr = napi_class;
typedef NapiMethodIDPtr = napi_method_id;
typedef NapiFieldIDPtr = napi_field_id;
typedef NapiThrowablePtr = napi_throwable;
typedef NapiStringPtr = napi_string;
typedef NapiArrayPtr = napi_array;

// Napi status enum
enum napi_status {
  napi_ok,
  napi_invalid_arg,
  napi_object_expected,
  napi_string_expected,
  napi_name_expected,
  napi_function_expected,
  napi_number_expected,
  napi_boolean_expected,
  napi_array_expected,
  napi_generic_failure,
  napi_pending_exception,
  napi_cancelled,
  napi_escape_called_twice,
  napi_handle_scope_mismatch,
  napi_callback_scope_mismatch,
  napi_queue_full,
  napi_closing,
  napi_bigint_expected,
  napi_date_expected,
  napi_arraybuffer_expected,
  napi_detachable_arraybuffer_expected,
  napi_would_deadlock,
  napi_no_external_buffers_allowed,
  napi_cannot_run_js,
  napi_invalid_utf8,
  napi_threadsafe_function_expected,
  napi_threadsafe_function_invalid_flags,
  napi_threadsafe_function_invalid_callback,
  napi_threadsafe_function_invalid_context,
  napi_threadsafe_function_invalid_thread_id,
  napi_threadsafe_function_invalid_handle,
  napi_threadsafe_function_invalid_handle_scope,
  napi_threadsafe_function_invalid_handle_scope_mismatch,
}

// FFI function signatures
typedef NativeGetVersion = ffi.Int32 Function();
typedef DartGetVersion = int Function();

typedef NativeDefineClass = ffi.Int32 Function(ffi.Pointer<ffi.Char>, NapiObjectPtr, ffi.Pointer<ffi.Int8>, ffi.Int32);
typedef DartDefineClass = int Function(ffi.Pointer<ffi.Char>, NapiObjectPtr, ffi.Pointer<ffi.Int8>, int);

typedef NativeFindClass = ffi.Int32 Function(ffi.Pointer<ffi.Char>);
typedef DartFindClass = int Function(ffi.Pointer<ffi.Char>);

typedef NativeGetSuperclass = ffi.Int32 Function(NapiClassPtr);
typedef DartGetSuperclass = int Function(NapiClassPtr);

typedef NativeFromReflectedMethod = ffi.Int32 Function(NapiObjectPtr);
typedef DartFromReflectedMethod = int Function(NapiObjectPtr);

typedef NativeFromReflectedField = ffi.Int32 Function(NapiObjectPtr);
typedef DartFromReflectedField = int Function(NapiObjectPtr);

typedef NativeToReflectedMethod = ffi.Int32 Function(NapiClassPtr, NapiMethodIDPtr, ffi.Uint8);
typedef DartToReflectedMethod = int Function(NapiClassPtr, NapiMethodIDPtr, int);

typedef NativeIsAssignableFrom = ffi.Int32 Function(NapiClassPtr, NapiClassPtr);
typedef DartIsAssignableFrom = int Function(NapiClassPtr, NapiClassPtr);

typedef NativeToReflectedField = ffi.Int32 Function(NapiClassPtr, NapiFieldIDPtr, ffi.Uint8);
typedef DartToReflectedField = int Function(NapiClassPtr, NapiFieldIDPtr, int);

typedef NativeThrow = ffi.Int32 Function(NapiThrowablePtr);
typedef DartThrow = int Function(NapiThrowablePtr);

typedef NativeThrowNew = ffi.Int32 Function(NapiClassPtr, ffi.Pointer<ffi.Char>);
typedef DartThrowNew = int Function(NapiClassPtr, ffi.Pointer<ffi.Char>);

// String operations
typedef NativeGetStringChars = ffi.Pointer<ffi.Char> Function(NapiStringPtr);
typedef DartGetStringChars = ffi.Pointer<ffi.Char> Function(NapiStringPtr);

typedef NativeGetStringLength = ffi.Int32 Function(NapiStringPtr);
typedef DartGetStringLength = int Function(NapiStringPtr);

typedef NativeReleaseStringChars = ffi.Void Function(NapiStringPtr, ffi.Pointer<ffi.Char>);
typedef DartReleaseStringChars = void Function(NapiStringPtr, ffi.Pointer<ffi.Char>);

typedef NativeDeleteGlobalRef = ffi.Void Function(NapiObjectPtr);
typedef DartDeleteGlobalRef = void Function(NapiObjectPtr);

typedef NativeNewString = ffi.Int32 Function(ffi.Pointer<ffi.Char>, ffi.Int32);
typedef DartNewString = int Function(ffi.Pointer<ffi.Char>, int);

// Global Napi Environment structure - based on JSI design pattern
final class GlobalNapiEnvStruct extends ffi.Struct {
  // Version and basic info
  external ffi.Pointer<ffi.NativeFunction<NativeGetVersion>> GetVersion;
  
  // Class operations
  external ffi.Pointer<ffi.NativeFunction<NativeDefineClass>> DefineClass;
  external ffi.Pointer<ffi.NativeFunction<NativeFindClass>> FindClass;
  external ffi.Pointer<ffi.NativeFunction<NativeGetSuperclass>> GetSuperclass;
  
  // Reflection operations
  external ffi.Pointer<ffi.NativeFunction<NativeFromReflectedMethod>> FromReflectedMethod;
  external ffi.Pointer<ffi.NativeFunction<NativeFromReflectedField>> FromReflectedField;
  external ffi.Pointer<ffi.NativeFunction<NativeToReflectedMethod>> ToReflectedMethod;
  external ffi.Pointer<ffi.NativeFunction<NativeToReflectedField>> ToReflectedField;
  
  // Exception operations
  external ffi.Pointer<ffi.NativeFunction<NativeThrow>> Throw;
  external ffi.Pointer<ffi.NativeFunction<NativeThrowNew>> ThrowNew;
  
  // String operations
  external ffi.Pointer<ffi.NativeFunction<NativeNewString>> NewString;
  external ffi.Pointer<ffi.NativeFunction<NativeGetStringLength>> GetStringLength;
  external ffi.Pointer<ffi.NativeFunction<NativeGetStringChars>> GetStringChars;
  external ffi.Pointer<ffi.NativeFunction<NativeReleaseStringChars>> ReleaseStringChars;
  
  // Object operations
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiObjectPtr)>> GetObjectClass;
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiObjectPtr, NapiClassPtr)>> IsInstanceOf;
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiObjectPtr)>> NewGlobalRef;
  
  // Array operations
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiArrayPtr)>> GetArrayLength;
  
  // Field operations
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiClassPtr, ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>> GetFieldID;
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiClassPtr, ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>> GetStaticFieldID;
  
  // Method operations
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiClassPtr, ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>> GetMethodID;
  external ffi.Pointer<ffi.NativeFunction<ffi.Int32 Function(NapiClassPtr, ffi.Pointer<ffi.Char>, ffi.Pointer<ffi.Char>)>> GetStaticMethodID;
}

@internal
class NapiObjectRefType {
  final int value;
  const NapiObjectRefType(this.value);
  
  static const NapiObjectRefType local = NapiObjectRefType(0);
  static const NapiObjectRefType global = NapiObjectRefType(1);
  static const NapiObjectRefType weakGlobal = NapiObjectRefType(2);
}

@internal
class NapiFinalizableHandle {
  final int value;
  const NapiFinalizableHandle(this.value);
  
  @override
  String toString() => 'NapiFinalizableHandle($value)';
}

// Simple ArkObject implementation without inheritance
class ArkObject {
  final NapiObjectPtr _object;
  
  ArkObject(this._object);
  
  NapiObjectPtr get reference => _object;
  
  bool get isNull => _object == ffi.Pointer.fromAddress(0);
  
  @override
  String toString() => 'ArkObject($_object)';
}

// ArkClass is defined in arkclass.dart part file

// Simple ArkString implementation without inheritance
class ArkString {
  final NapiStringPtr _string;
  
  ArkString(this._string);
  
  NapiStringPtr get reference => _string;
  
  bool get isNull => _string == ffi.Pointer.fromAddress(0);
  
  @override
  String toString() => 'ArkString($_string)';
}

// Simple ArkArray implementation without inheritance
class ArkArray {
  final NapiArrayPtr _array;
  
  ArkArray(this._array);
  
  NapiArrayPtr get reference => _array;
  
  bool get isNull => _array == ffi.Pointer.fromAddress(0);
  
  @override
  String toString() => 'ArkArray($_array)';
}
@internal
abstract class ArkObjType<T extends ArkObject?> extends ArkType<T>
    with ArkCallable<T, T>, ArkConstructable<T, T>, ArkAccessible<T, T> {
  int get superCount;
  ArkObjType get superType;
  ArkObjType<T?> get nullableType;
  bool get isNullable => this == nullableType;
  const ArkObjType();
  T fromReference(ArkReference reference);

  ArkClass get jClass {
    if (signature.startsWith('L') && signature.endsWith(';')) {
      return ArkClass.forName(signature.substring(1, signature.length - 1));
    }
    return ArkClass.forName(signature);
  }

  @override
  T _staticCall(JClassPtr clazz, JMethodIDPtr methodID, Pointer<JValue> args) {
    final result = Jni.env.CallStaticObjectMethodA(clazz, methodID, args);
    return fromReference(ArkGlobalReference(result));
  }

  @override
  T _instanceCall(JObjectPtr obj, JMethodIDPtr methodID, Pointer<JValue> args) {
    return fromReference(
        ArkGlobalReference(Jni.env.CallObjectMethodA(obj, methodID, args)));
  }

  @override
  T _newObject(JClassPtr clazz, JMethodIDPtr methodID, Pointer<JValue> args) {
    return fromReference(
        ArkGlobalReference(Jni.env.NewObjectA(clazz, methodID, args)));
  }

  @override
  T _instanceGet(JObjectPtr obj, JFieldIDPtr fieldID) {
    return fromReference(
        ArkGlobalReference(Jni.env.GetObjectField(obj, fieldID)));
  }

  @override
  void _instanceSet(JObjectPtr obj, JFieldIDPtr fieldID, T? val) {
    final valRef = val?.reference ?? arkNullReference;
    Jni.env.SetObjectField(obj, fieldID, valRef.pointer);
  }

  @override
  T _staticGet(JClassPtr clazz, JFieldIDPtr fieldID) {
    return fromReference(
        ArkGlobalReference(Jni.env.GetStaticObjectField(clazz, fieldID)));
  }

  @override
  void _staticSet(JClassPtr clazz, JFieldIDPtr fieldID, T? val) {
    final valRef = val?.reference ?? arkNullReference;
    Jni.env.SetStaticObjectField(clazz, fieldID, valRef.pointer);
  }
}

ArkObjType<dynamic> _lowestCommonAncestor(
    ArkObjType<dynamic> a, ArkObjType<dynamic> b) {
  if (a is! ArkObjType<ArkObject> || b is! ArkObjType<ArkObject>) {
    a = a.nullableType;
    b = b.nullableType;
  }
  while (a.superCount > b.superCount) {
    a = a.superType;
  }
  while (b.superCount > a.superCount) {
    b = b.superType;
  }
  while (a != b) {
    a = a.superType;
    b = b.superType;
  }
  return a;
}

@internal
ArkObjType<dynamic> lowestCommonSuperType(List<ArkObjType<dynamic>> types) {
  return types.reduce(_lowestCommonAncestor);
}


